package com.ctsi.commons.util.crypto;

import com.ctsi.commons.util.GeneralException;
import org.bouncycastle.crypto.BlockCipher;
import org.bouncycastle.crypto.BufferedBlockCipher;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.engines.AESEngine;
import org.bouncycastle.crypto.modes.CBCBlockCipher;
import org.bouncycastle.crypto.paddings.BlockCipherPadding;
import org.bouncycastle.crypto.paddings.PKCS7Padding;
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.crypto.params.ParametersWithIV;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import java.security.SecureRandom;
import java.security.Security;

/**
 * aes 256 CBC 加密
 * 如果采用jce体系,需要获取无限制文件,这里直接采用bc API
 * @author yueming
 * 
 */
public class Aes256Crypt implements SymmetryCrypt  {

	public static int register() {
		return Security.addProvider(new BouncyCastleProvider());
	}

	private int deaultKeySize = 256;

	public void setDeaultKeySize(int deaultKeySize) {
		this.deaultKeySize = deaultKeySize;
	}

	private String algorithm = "AES";

	@Override
	public String getAlgorithm() {
		return algorithm;
	}

	public void setAlgorithm(String algorithm) {
		this.algorithm = algorithm;
	}

	private String transformation = "AES/CBC/PKCS7Padding";

	@Override
	public String getTransformation() {
		return transformation;
	}

 

	@Override
	public int getDeaultKeySize() {

		return deaultKeySize;
	}
	
 

 
 

 

	private static CipherParameters getCipherParameters(byte[] key) {
		byte[] iv = { 0x38, 0x37, 0x36, 0x35, 0x34, 0x33, 0x32, 0x31, 0x38, 0x37, 0x36, 0x35, 0x34, 0x33, 0x32, 0x31 };
		// CipherParameters cipherParameters = new KeyParameter(key);//ParametersWithIV
		CipherParameters cipherParameters = new ParametersWithIV(new KeyParameter(key), iv);
		return cipherParameters;
	}

	private static BufferedBlockCipher getBufferedBlockCipher() {
		/*
		 * A full list of BlockCiphers can be found at http://www.bouncycastle.org/docs/docs1.6/org/bouncycastle/crypto/BlockCipher.html
		 */
		BlockCipher blockCipher = new AESEngine();

		/*
		 * Paddings available (http://www.bouncycastle.org/docs/docs1.6/org/bouncycastle/crypto/paddings/BlockCipherPadding.html): - ISO10126d2Padding -
		 * ISO7816d4Padding - PKCS7Padding - TBCPadding - X923Padding - ZeroBytePadding
		 */
		// BlockCipherPadding blockCipherPadding = new ZeroBytePadding();
		BlockCipherPadding blockCipherPadding = new PKCS7Padding();

		BufferedBlockCipher bufferedBlockCipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(blockCipher), blockCipherPadding);

		// BufferedBlockCipher cipher = new PaddedBufferedBlockCipher( new CBCBlockCipher(new AESEngine()), padding);
		return bufferedBlockCipher;
	}

	public static byte[] encryptAES256(byte[] key, byte[] input,int off, int len) throws InvalidCipherTextException {
		assert key.length == 32; // 32 bytes == 256 bits
		CipherParameters cipherParameters = getCipherParameters(key);

		BufferedBlockCipher bufferedBlockCipher = getBufferedBlockCipher();
		boolean forEncryption = true;
		return process(input,off,len, bufferedBlockCipher, cipherParameters, forEncryption);
	}

	public static byte[] decryptAES256(byte[] key, byte[] input,  int off, int len) throws InvalidCipherTextException {
		assert key.length == 32; // 32 bytes == 256 bits
		CipherParameters cipherParameters = getCipherParameters(key);

		BufferedBlockCipher bufferedBlockCipher = getBufferedBlockCipher();

		boolean forEncryption = false;
		return process(input,off,  len, bufferedBlockCipher, cipherParameters, forEncryption);
	}

	private static byte[] process(byte[] input, int inputOffset, int inputLength,BufferedBlockCipher bufferedBlockCipher, CipherParameters cipherParameters, boolean forEncryption) throws InvalidCipherTextException {
		bufferedBlockCipher.init(forEncryption, cipherParameters);

 

		int maximumOutputLength = bufferedBlockCipher.getOutputSize(inputLength);
		byte[] output = new byte[maximumOutputLength];
		int outputOffset = 0;
		int outputLength = 0;

		int bytesProcessed;

		bytesProcessed = bufferedBlockCipher.processBytes(input, inputOffset, inputLength, output, outputOffset);
		outputOffset += bytesProcessed;
		outputLength += bytesProcessed;

		bytesProcessed = bufferedBlockCipher.doFinal(output, outputOffset);
		outputOffset += bytesProcessed;
		outputLength += bytesProcessed;

		if (outputLength == output.length) {
			return output;
		} else {
			byte[] truncatedOutput = new byte[outputLength];
			System.arraycopy(output, 0, truncatedOutput, 0, outputLength);
			return truncatedOutput;
		}
	}

	@Override
	public SecretKey generateKey() throws Exception {
		return generateKey(getDeaultKeySize(), null);
	}

	@Override
	public SecretKey generateKey(int size, SecureRandom secr) throws Exception {
		KeyGenerator keyGen = KeyGenerator.getInstance(getAlgorithm(),"BC");
		if (secr != null) {
			keyGen.init(size, secr); // 初始化
		} else {
			keyGen.init(size); // 初始化
		}
 
		return keyGen.generateKey();
	}

	@Override
	public byte[] encrypt(SecretKey key, byte[] bytes) throws GeneralException {
 
		try {
			return encryptAES256(key.getEncoded(),bytes,0,bytes.length);
		} catch (InvalidCipherTextException e) {
            throw new    GeneralException(e);
		}
	}

	@Override
	public byte[] encrypt(SecretKey key, byte[] bytes, int off, int len) throws GeneralException {
		try {
			return encryptAES256(key.getEncoded(),bytes,off,len);
		} catch (InvalidCipherTextException e) {
            throw new    GeneralException(e);
		}
	}

	@Override
	public byte[] decrypt(SecretKey key, byte[] bytes, int off, int len) throws GeneralException {
		try {
			return decryptAES256(key.getEncoded(),bytes,off,len);
		} catch (InvalidCipherTextException e) {
            throw new    GeneralException(e);
		}
	}

	@Override
	public byte[] decrypt(SecretKey key, byte[] bytes) throws GeneralException {
		try {
			return decryptAES256(key.getEncoded(),bytes,0,bytes.length);
		} catch (InvalidCipherTextException e) {
            throw new    GeneralException(e);
		}
	}

}
